package com.demo.config;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.stream.IntStream;

/**
 * 序列化与反序列化配置
 */
@Configuration
public class JacksonProdConfig {

    private static final String TIMEZONE_ID = "Asia/Shanghai";

    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();

        // 注册核心模块
        mapper.registerModules(
                new ParameterNamesModule(),
                new Jdk8Module() // 补充Optional等支持
        );

        // 日期配置（使用JavaTimeModule的序列化方式）
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper.registerModule(new JavaTimeModule()
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)));

        // 空值处理
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        // 未知属性忽略
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // 安全配置 ======start=======

        // 设置流读取约束
        mapper.getFactory().setStreamReadConstraints(
                StreamReadConstraints.builder()
                        .maxStringLength(100000)
                        .maxNestingDepth(20)
                        .build()
        );
        // 启用严格重复检测
        mapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
        mapper.disable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature());

        // 安全配置 ======end=======

        // 时区设置
        mapper.setTimeZone(TimeZone.getTimeZone(TIMEZONE_ID));

        // 中文编码支持
        mapper.getFactory().setCharacterEscapes(new SecureHtmlEscapes());

        return mapper;
    }

    /**
     * 加固版HTML字符转义器
     */
    public static class SecureHtmlEscapes extends CharacterEscapes {
        private final int[] asciiEscapes;
        private final Map<Integer, SerializableString> customEscapes = new HashMap<>();

        public SecureHtmlEscapes() {
            // 1. 继承JSON标准转义集
            asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON();

            // 2. 添加HTML特殊字符转义
            addEscape('<', "&lt;");
            addEscape('>', "&gt;");
            addEscape('&', "&amp;");
            addEscape('"', "&quot;");
            addEscape('\'', "&#39;");
            addEscape('/', "&#x2F;"); // 防止XSS闭合标签

            // 3. 添加控制字符转义（防HTTP响应分割）
            IntStream.rangeClosed(0x00, 0x1F)
                    .filter(ch -> ch != '\t' && ch != '\n' && ch != '\r')
                    .forEach(ch -> addEscape(ch, "\\u" + String.format("%04X", ch)));
        }

        private void addEscape(int charCode, String escape) {
            asciiEscapes[charCode] = ESCAPE_CUSTOM;
            customEscapes.put(charCode, new SerializedString(escape));
        }

        @Override
        public int[] getEscapeCodesForAscii() {
            return asciiEscapes;
        }

        @Override
        public SerializableString getEscapeSequence(int ch) {
            // 优先检查自定义转义
            if (customEscapes.containsKey(ch)) {
                return customEscapes.get(ch);
            }

            // 处理非ASCII字符（中文等）
            if (ch > 0x7F) {
                return new SerializedString("&#" + ch + ";");
            }

            return null; // 其他字符不转义
        }
    }

}


